package gamePlus.server;

import java.util.LinkedList;

import org.omg.PortableInterceptor.NON_EXISTENT;

import game.Player;
import gamePlus.GameSettings;
import carddeck.Card;
import carddeck.Rank;
import carddeck.Suit;
import carddeckPlus.CardDeckPlus;

public class RoundUnit {
	enum ServerState {
		NONE, AWAITING_INITIAL, AWAITING_MOVE, AFTERDRAW, AWAITING_JACK_SELECTION, AWAITING_ACE_SELECTION;
	}
	
	private GameServer server;
	private GameSettings settings;
	private CardDeckPlus freshCards;
	private CardDeckPlus usedCards;
	private LinkedList<Player> players;
	
	private ServerState serverState;
	private Rank demand;
	private Player demandedBy;
	private int cardPenalty;
	private int waitPenalty;
	private Player currentPlayer;
	
	private boolean gameEnds;
	private Player roundWinner;
	
	public RoundUnit(GameServer server, LinkedList<Player> players) {
		this.server=server;
		this.players=players;
		settings = server.getServerSettings();
		serverState=ServerState.NONE;
		initializeDecks();
		initialGiveOut();
	}

	private void initializeDecks() {
		freshCards = new CardDeckPlus(settings.jkrsPerDeck);
		usedCards = new CardDeckPlus(settings.jkrsPerDeck);
		for(int i=0;i<settings.carddecksUsed;i++)
			freshCards.addNewDeck();
		server.updateFreshCount(freshCards.getCardCount());
	}
	
	private void initialGiveOut() {
		LinkedList<Card> cardsGivenOut = new LinkedList<>();
		for(int i=0;i<settings.startingCardsCount;i++)
			for(Player thePlayer: players) {
				cardsGivenOut.add(giveCardTo(thePlayer));
			}
		server.initialGiveOutComplete(cardsGivenOut);
		try {
			Thread.sleep(cardsGivenOut.size()*50+2000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		postGiveOut();
	}

	void postGiveOut() {
		do {
			if (freshCards.getCardCount() <= 0)
				refreshCards();
			Card theCard = freshCards.getFirstCard();
			usedCards.addCard(theCard);
			server.postGiveOut(theCard);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		} while (!usedCards.peekLastCard().bIsNeutral());
		serverState = ServerState.AWAITING_INITIAL;
	}
	
	private Card giveCardTo(Player player) {
		Card theCard = freshCards.getFirstCard();
		player.addCard(theCard);
		if(freshCards.getCardCount()==0)
			refreshCards();
		return theCard;
	}
	
	private void refreshCards() {
		if (usedCards.getCardCount() < 10) {
			freshCards.addNewDeck();
			server.updateFreshCount(freshCards.getCardCount());
			return;
		}
		LinkedList<Card> usedCardSet = usedCards.getCards();
		Card lastCard = usedCardSet.getLast();
		usedCardSet.removeLast();
		for(Card card : usedCardSet)
			card.setFakeCard(null);
		freshCards.appendCards(usedCardSet);
		usedCardSet.clear();
		usedCardSet.add(lastCard);
		server.usedRefreshed();
		freshCards.shuffleDeck();
		server.updateFreshCount(freshCards.getCardCount());
	}
	
	synchronized void initialPlayCards(String playerName, LinkedList<Card> cardsToPlay) {
		if(serverState!=ServerState.AWAITING_INITIAL)
			return;
		if(GameRules.canPlaySetOn(cardsToPlay, usedCards.peekLastCard(), cardPenalty, waitPenalty, demand)) {
			serverState=ServerState.AWAITING_MOVE;
			currentPlayer = getPlayerNamed(playerName);
			playCards(playerName, cardsToPlay);
		}
	}
	
	synchronized void playCards(String playerName, LinkedList<Card> cardsToPlay) {
		if(getPlayerNamed(playerName)!=currentPlayer||serverState!=ServerState.AWAITING_MOVE&&serverState!=ServerState.AFTERDRAW)
			return;
		if(GameRules.canPlaySetOn(cardsToPlay, usedCards.peekLastCard(), cardPenalty, waitPenalty, demand)&&(serverState!=ServerState.AFTERDRAW||cardsToPlay.getFirst().isEqualTo(freshCards.peekFirstCard()))) {
			usedCards.appendCards(cardsToPlay);
			if(serverState==ServerState.AFTERDRAW)
				freshCards.getFirstCard();
			for(Card playedCard:cardsToPlay) {
				if(serverState==ServerState.AFTERDRAW&&playedCard==cardsToPlay.getFirst())
					continue;
				for(Card theCard: currentPlayer.getCards()) {
					if(theCard.isEqualTo(playedCard)) {
						currentPlayer.getCards().remove(theCard);
						break;
					}
				}
			}
			server.cardsPlayed(playerName, cardsToPlay);
			if(cardsToPlay.getLast().getRealCard().getCardRank()==Rank.JACK) {
				serverState = ServerState.AWAITING_JACK_SELECTION;
				server.requestJackSelection(playerName);
			}
			else if(cardsToPlay.getLast().getRealCard().getCardRank()==Rank.ACE) {
				serverState = ServerState.AWAITING_ACE_SELECTION;
				server.requestAceSelection(playerName);
			}
			else {
				playedCards(cardsToPlay);
				demandCheck();
				nextPlayer();
			}
		}
		else
			System.out.println("illegal move attempted");
	}
	
	synchronized void drawCards(String playerName) {
		if(getPlayerNamed(playerName)!=currentPlayer||serverState!=ServerState.AWAITING_MOVE&&serverState!=ServerState.AFTERDRAW)
			return;
		if(serverState==ServerState.AFTERDRAW) {
			giveCardTo(currentPlayer);
			server.playerAfterdrawn(playerName);
			demandCheck();
			nextPlayer();
		}
		else {
			if(cardPenalty==0) {
				server.drawCard(freshCards.peekFirstCard());
				serverState=ServerState.AFTERDRAW;
			}
			else {
				LinkedList<Card> cardsDrawn = new LinkedList<>();
				while(cardPenalty>0) {
					cardsDrawn.add(giveCardTo(currentPlayer));
					cardPenalty--;
				}
				server.drawCards(cardsDrawn);
				try {
					Thread.sleep(cardsDrawn.size()*50+1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				nextPlayer();
			}
		}
	}
	
	synchronized void wait(String playerName) {
		if(getPlayerNamed(playerName)!=currentPlayer||serverState!=ServerState.AWAITING_MOVE||waitPenalty==0)
			return;
		currentPlayer.setWaitTime(waitPenalty-1);
		server.setWaitTime(currentPlayer.getName(), currentPlayer.getWaitTime());
		waitPenalty=0;
		server.updateWaitPenalty(0);
		nextPlayer();
	}
	
	private void demandCheck() {
		if(demandedBy==currentPlayer) {
			demand=null;
			demandedBy=null;
			server.clearDemand();
		}
	}
	
	private void nextPlayer() {
		if(gameEnds||currentPlayer.getCards().isEmpty()) {
			if(gameEnds||cardPenalty==0) {
				String[] names = new String[players.size()];
				int[] points = new int[players.size()];
				for(int i=0;i<players.size();i++) {
					Player thePlayer = players.get(i);
					names[i] = thePlayer.getName();
					if(thePlayer==roundWinner)
						points[i]=50;
					else
						points[i]=thePlayer.getPoints();
				}
				RoundResults results = new RoundResults(names, points);
				server.getGameUnit().roundEnded(results);
				return;
			}
			roundWinner = currentPlayer;
			gameEnds=true;
		}
		Player nextPlayer = getNextPlayer(currentPlayer);
		while(nextPlayer.getWaitTime()>0||nextPlayer.getWaitTime()==0&&cardPenalty>0&&getTopmostUsedCard().getRealCard().isEqualTo(Rank.KING, Suit.SPADES)) {
			if(cardPenalty>0&&getTopmostUsedCard().getRealCard().isEqualTo(Rank.KING, Suit.SPADES)) {
				nextPlayer.setWaitTime(nextPlayer.getWaitTime()+1);
				server.setWaitTime(nextPlayer.getName(), nextPlayer.getWaitTime());
			}
			else {
				nextPlayer.setWaitTime(nextPlayer.getWaitTime()-1);
				server.setWaitTime(nextPlayer.getName(), nextPlayer.getWaitTime());
			}
			nextPlayer = getNextPlayer(nextPlayer);
		}
		currentPlayer=nextPlayer;
		serverState = ServerState.AWAITING_MOVE;
		server.nextPlayer(currentPlayer.getName());
	}

	private Player getNextPlayer(Player after) {
		if(cardPenalty>0&&getTopmostUsedCard().getRealCard().isEqualTo(Rank.KING, Suit.SPADES))
			return after.prevPlayer;
		else
			return after.nextPlayer;
	}
	
	private void playedCards(LinkedList<Card> cardsPlayed) {
		int savedCardPenalty = cardPenalty;
		int savedWaitPenalty = waitPenalty;
		if(cardsPlayed.getLast().getRealCard().getCardRank()==Rank.TWO)
			cardPenalty+=cardsPlayed.size()*2;
		else if(cardsPlayed.getLast().getRealCard().getCardRank()==Rank.THREE)
			cardPenalty+=cardsPlayed.size()*3;
		else if(cardsPlayed.getLast().getRealCard().getCardRank()==Rank.FOUR)
			waitPenalty+=cardsPlayed.size();
		else if(cardsPlayed.getFirst().getRealCard().isEqualTo(Rank.QUEEN, Suit.SPADES)) {
			cardPenalty=0;
			waitPenalty=0;
		}
		else if(cardsPlayed.getLast().getRealCard().getCardRank()==Rank.KING&&(cardsPlayed.getLast().getRealCard().getCardSuit()==Suit.SPADES||cardsPlayed.getLast().getRealCard().getCardSuit()==Suit.HEARTS)&&demand==null) {
			for (int i=cardsPlayed.size()-1;i>=0;i--) {
				if(cardsPlayed.get(i).isOffensive())
					cardPenalty+=5;
				else
					break;
			}
		}
		if(savedCardPenalty!=cardPenalty)
			server.updateCardPenalty(cardPenalty);
		if(savedWaitPenalty!=waitPenalty)
			server.updateWaitPenalty(waitPenalty);
	}

	private Card getTopmostUsedCard() {
		return usedCards.peekLastCard();
	}
	
	synchronized void selectDemand(String playerName, Rank demand) {
		if(getPlayerNamed(playerName)!=currentPlayer&&currentPlayer!=null||serverState!=ServerState.AWAITING_JACK_SELECTION)
			return;
		this.demand = demand;
		this.demandedBy = getPlayerNamed(playerName);
		server.demandStarted(demand, playerName);
		nextPlayer();
	}
	
	synchronized void selectColor(String playerName, Suit color) {
		if(getPlayerNamed(playerName)!=currentPlayer&&currentPlayer!=null||serverState!=ServerState.AWAITING_ACE_SELECTION)
			return;
		usedCards.peekLastCard().setFakeCard(new Card(color, Rank.ACE));
		server.colorSet(color);
		nextPlayer();
	}
	
	private Player getPlayerNamed(String name) {
		for(Player player: players)
			if(player.getName().equals(name))
				return player;
		throw new UnsupportedOperationException("Nie znaleziono gracza o podanym imieniu");
	}

	public void skipInitial(String nameOf) {
		getPlayerNamed(nameOf).setSkippedInitial(true);
		if(allSkipped()) {
			postGiveOut();
			for(Player player: players)
				player.setSkippedInitial(false);
		}
	}
	
	private boolean allSkipped() {
		for(Player player: players)
			if(!player.hasSkippedInitial())
				return false;
		return true;
	}
}
